package service.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import models.Actor;
import models.Doc;
import models.Doc.Category;
import models.Doc.Mandatory;
import models.Doc.Type;
import models.LoanApp;
import models.LoanApp.LoanType;

import org.apache.commons.lang.ArrayUtils;

import prefix.integration.wm.AbstractWalletManager;
import prefix.util.OverallUtil;
import processor.ActorProcessor;
import repository.ActorRepository;
import repository.DocRepository;
import repository.LoanAppRepository;
import service.ActorService;
import service.DocService;
import service.LoanAppService;

public class ActorServiceImpl extends AbstractServiceImpl implements ActorService {

    protected Actor dbActor;

    public ActorServiceImpl() {
        this.dbActor = new Actor();
    }

    public ActorServiceImpl(Actor actor) {
        this.dbActor = actor;
    }

    @Override
    public ActorProcessor getProcessor() {
        return new ActorProcessor(this.dbActor);
    }

    @Override
    public Actor getDBActor() {
        // TODO Auto-generated method stub
        return this.dbActor;
    }

    @Override
    public LoanAppService getCurrentLoanApplication() {
        return LoanAppRepository.getCurrentLoanApp(this.dbActor.getId());
    }

    public double getAvailableCash() {
        BigDecimal walletBalance = AbstractWalletManager.getInstance().getBalance(this.dbActor.getId());
        double pendingHoldBalance = ActorRepository.getPendingHoldBalance(this.dbActor.getId());
        double availableBalance = walletBalance.doubleValue() - pendingHoldBalance;
        return OverallUtil.round2Decimal(availableBalance, RoundingMode.FLOOR).doubleValue();
    }

    public Map<Doc.Type, DocService> getApplicableBorrowerDocs(LoanType loanType) {

        List<DocService> docs = new ArrayList<DocService>();

        // Create default LCDocImpl for required documents depending on loan
        // type
        // Add required documents
        docs.addAll(getRequiredDocuments(loanType));

        // Add optional documents
        docs.addAll(getOptionalDocuments(loanType));

        // Overwrite fake LCDocImpl if there is one in the db for the same doc
        // type

        docs.addAll(DocRepository.getDocByCategory(this.dbActor.getId(), Category.BORROWER_DOCS));

        return convertDocImplListToMap(docs);
    }

    /**
     * Get all documents status for this loan
     * 
     * @throws Throwable
     */
    public Map<Doc.Type, DocService> getAllBorrowerDocs(LoanType loanType) {
        List<DocService> docs = new ArrayList<DocService>();

        // Create default empty documents
        for (Doc.Type docType : Doc.Type.getDocumentTypesByCategory(Category.BORROWER_DOCS)) {

            // Create empty document to hold status
            Doc dbDoc = new Doc();

            dbDoc.setupEmplyDoc(this.dbActor.getId(), null, Doc.Status.NONE, Mandatory.NO, Category.BORROWER_DOCS,
                    docType);
            docs.add(new DocServiceImpl(dbDoc));
        }

        // Create default LCDocImpl for required documents depending on loan
        // type
        // Add required documents
        docs.addAll(getRequiredDocuments(loanType));

        // Add optional documents
        docs.addAll(getOptionalDocuments(loanType));

        // Overwrite fake LCDocImpl if there is one in the db for the same doc
        // type

        docs.addAll(DocRepository.getDocByCategory(this.getDBActor().getId(), Category.BORROWER_DOCS));

        return convertDocImplListToMap(docs);
    }

    private static Map<LoanApp.LoanType, Doc.Type[]> REQUIREDDOCSBYLOANTYPE = new HashMap<LoanApp.LoanType, Doc.Type[]>();

    private static Map<LoanApp.LoanType, Doc.Type[]> OPTIONALDOCSBYLOANTYPE = new HashMap<LoanApp.LoanType, Doc.Type[]>();

    public static Doc.Type[] GENERALVERIFICATION;

    public static Doc.Type[] REQUIREDDOCS;

    public static Doc.Type[] OPTIONALDOCS;
    static {
        // Required docs for personal loans

        GENERALVERIFICATION = new Doc.Type[] { Type.REGISTER_EMAIL, Type.MOBILE, Type.COMPANY_PHONE };

        REQUIREDDOCS = new Doc.Type[] { Type.IDENTITY_CARD, Type.BANK_STATEMENT, Type.BANK_CARD,
                Type.RESIDENCE_REGISTRATION, Type.IDENTITY_CARD, Type.BANK_STATEMENT, Type.BANK_CARD,
                Type.RESIDENCE_REGISTRATION, Type.COM_BANK_STATEMENT, Type.COM_CERT_ORGANIZATION_CODE,
                Type.COM_COMMITMENT_LETTER, Type.COM_REVENUE_LICENSE, Type.COM_REVENUE_PROOF,
                Type.COM_TAX_REGISTRATION_CERT };

        OPTIONALDOCS = new Doc.Type[] { Type.PBOC_CREDIT_REPORT, Type.PROOF_OF_SALARY, Type.LOAN_PURPOSE_SUPPORT,
                Type.LAST_YEAR_TAX_RETURNS, Type.PROPERTY_TITLE_CERTIFICATE, Type.MARRIAGE_CERTIFICATE,
                Type.PROOF_OF_OTHER_LIQUID_ASSETS, Type.OTHER_DOCUMENTS, Type.PBOC_CREDIT_REPORT,
                Type.COM_MARKTING_CONTRACT, Type.COM_ELECTRICITY_BILL, Type.MARRIAGE_CERTIFICATE,
                Type.PROPERTY_TITLE_CERTIFICATE, Type.COM_ASSET_PROOF_PAPER, Type.LOAN_PURPOSE_SUPPORT,
                Type.COM_PBOC_CREDIT_REPORT, Type.COM_CAPITAL_VERIFICATION_REPORT, Type.COM_ASSOCIATION_ARTICLES,
                Type.OTHER_DOCUMENTS };

        REQUIREDDOCSBYLOANTYPE.put(LoanType.PERSONAL, new Doc.Type[] { Type.IDENTITY_CARD, Type.BANK_STATEMENT,
                Type.BANK_CARD, Type.RESIDENCE_REGISTRATION });

        // Required docs for business loans
        REQUIREDDOCSBYLOANTYPE.put(LoanType.BUSINESS, new Doc.Type[] { Type.IDENTITY_CARD, Type.BANK_STATEMENT,
                Type.BANK_CARD, Type.RESIDENCE_REGISTRATION, Type.COM_BANK_STATEMENT, Type.COM_CERT_ORGANIZATION_CODE,
                Type.COM_COMMITMENT_LETTER, Type.COM_REVENUE_LICENSE, Type.COM_REVENUE_PROOF,
                Type.COM_TAX_REGISTRATION_CERT });

        // Optional docs for personal loans
        OPTIONALDOCSBYLOANTYPE.put(LoanType.PERSONAL, new Doc.Type[] { Type.PBOC_CREDIT_REPORT, Type.PROOF_OF_SALARY,
                Type.LOAN_PURPOSE_SUPPORT, Type.LAST_YEAR_TAX_RETURNS, Type.PROPERTY_TITLE_CERTIFICATE,
                Type.MARRIAGE_CERTIFICATE, Type.PROOF_OF_OTHER_LIQUID_ASSETS, Type.OTHER_DOCUMENTS });

        // Optional docs for business loans
        OPTIONALDOCSBYLOANTYPE.put(LoanType.BUSINESS, new Doc.Type[] { Type.PBOC_CREDIT_REPORT,
                Type.COM_MARKTING_CONTRACT, Type.COM_ELECTRICITY_BILL, Type.MARRIAGE_CERTIFICATE,
                Type.PROPERTY_TITLE_CERTIFICATE, Type.COM_ASSET_PROOF_PAPER, Type.LOAN_PURPOSE_SUPPORT,
                Type.COM_PBOC_CREDIT_REPORT, Type.COM_CAPITAL_VERIFICATION_REPORT, Type.COM_ASSOCIATION_ARTICLES,
                Type.OTHER_DOCUMENTS });
    }

    private List<DocService> getRequiredDocuments(LoanType loanType) {
        List<DocService> docs = new ArrayList<DocService>();
        Doc.Type[] requiredDoc;
        if (LoanType.ANY.equals(loanType))
            requiredDoc = REQUIREDDOCS;
        else
            requiredDoc = REQUIREDDOCSBYLOANTYPE.get(loanType);
        if (requiredDoc != null) {
            for (Doc.Type docType : requiredDoc) {

                // Create empty document to hold status
                Doc dbDoc = new Doc();

                dbDoc.setupEmplyDoc(this.dbActor.getId(), null, Doc.Status.REQUESTED, Mandatory.YES,
                        Category.BORROWER_DOCS, docType);

                docs.add(new DocServiceImpl(dbDoc));
            }
        }
        return docs;
    }

    private List<DocService> getOptionalDocuments(LoanType loanType) {
        List<DocService> docs = new ArrayList<DocService>();
        Doc.Type[] optionalDocs;
        if (LoanType.ANY.equals(loanType))
            optionalDocs = OPTIONALDOCS;
        else
            optionalDocs = OPTIONALDOCSBYLOANTYPE.get(loanType);
        if (optionalDocs != null) {
            for (Doc.Type docType : optionalDocs) {

                // Create empty document to hold status
                Doc dbDoc = new Doc();

                dbDoc.setupEmplyDoc(this.dbActor.getId(), null, Doc.Status.REQUESTED, Mandatory.NO,
                        Category.BORROWER_DOCS, docType);

                docs.add(new DocServiceImpl(dbDoc));
            }
        }
        return docs;
    }

    /**
     * Tells whether the given document type is required for this loan type
     * 
     * @param docType
     */
    public boolean isRequiredDocument(Type docType) {

        if (REQUIREDDOCS != null) {
            return ArrayUtils.contains(REQUIREDDOCS, docType);
        }
        return false;
    }

    /**
     * Tells whether the given document type is optional for this loan type
     * 
     * @param docType
     */
    public boolean isOptionalDocument(Type docType) {

        if (OPTIONALDOCS != null) {
            return ArrayUtils.contains(OPTIONALDOCS, docType);
        }
        return false;
    }

    /**
     * @Lucas
     * 
     * @param docImplList
     * @return
     */
    private static Map<Doc.Type, DocService> convertDocImplListToMap(List<DocService> docImplList) {
        Map<Doc.Type, DocService> map = new HashMap<Doc.Type, DocService>();
        Map<Doc.Type, DocService> sortedMap = new LinkedHashMap<Doc.Type, DocService>();

        if (docImplList == null || docImplList.size() <= 0) {
            return map;
        }
        for (DocService docServoce : docImplList) {
            map.put(docServoce.getDbdoc().getType(), docServoce);
        }
        // Sort documents by type in the order they are defined in TLDoc.Type
        for (Doc.Type docType : Doc.Type.getDocumentTypesByCategory(Category.BORROWER_DOCS)) {
            if (map.containsKey(docType)) {
                sortedMap.put(docType, map.get(docType));
            }
        }
        return sortedMap;
    }

}
